<?php

// @formatter:off
/**
 * @file WCQuery.php
 * @author Alejandro Dario Simi
 * @date $Date: 2014-06-03 03:40:32 +0000 (Tue, 03 Jun 2014) $
 *
 * $Id: WCQuery.php 112 2014-06-03 03:40:32Z daemonraco@gmail.com $
 * $URL: http://wcomix.googlecode.com/svn/trunk/includes/search/WCQuery.php $
 */
// @formatter:on

class WCQuery {
	//
	// Constants.
	const ModeExact = "exact";
	const ModePartial = "partial";
	const OperationFilter = "filter";
	const OperationSearch = "search";
	const TypeCollection = "cl";
	const TypeDirectory = "di";
	const TypeTag = "ta";
	//
	// Public class properties.
	//
	// Protected class properties.
	//
	// Protected properties.	
	protected $_analyzed = false;
	/**
	 * @var WCConnection Database pointer shortcut.
	 */
	protected $_db;
	/**
	 * @var string Database tables' prefix shortcut.
	 */
	protected $_dbprefix;
	protected $_queryTerms = array(
		"kinds" => array(),
		"anykind" => true
	);
	//
	// Magic methods.
	public function __construct() {
		//
		// Loading shortcuts.
		// @{
		$this->_db = WCConnection::Instance();
		$this->_dbprefix = $this->_db->prefix();
		// @}
		$this->completeQueryTermsStruct();
	}
	public function __toString() {
		return $this->queryString();
	}
	//
	// Public methods.
	public function analyzed() {
		return $this->_analyzed;
	}
	public function analyzeQuery($query) {
		$terms = explode(" ", strtolower($query));
		array_walk($terms, function(&$item) {
			$item = trim(str_replace("_", " ", $item));
		});
		$terms = array_unique(array_filter($terms));

		$matches = false;

		$typesSubpattern = self::TypeCollection."|".self::TypeDirectory."|".self::TypeTag;
		foreach($terms as $pos => $term) {
			if(preg_match("/^only:(?P<kind>.*)/", $term, $matches)) {
				if(in_array($matches["kind"], array("comics", "images"))) {
					$this->_queryTerms["kinds"][] = $matches["kind"];
					$this->_queryTerms["anykind"] = false;
				}
				continue;
			}

			preg_match("/^(?P<operation>(-)?)((?P<type>{$typesSubpattern}):)?(?P<term>.*)/", $term, $matches);

			$type = self::TypeTag;
			if($matches["type"] != "") {
				$type = $matches["type"];
			}
			$termOperation = self::OperationSearch;
			if($matches["operation"] == "-") {
				$termOperation = self::OperationFilter;
			}
			$termMode = self::ModeExact;
			if(preg_match("/\*/", $matches["term"])) {
				$termMode = self::ModePartial;
			}

			$this->_queryTerms[$termOperation][$type][$termMode][$pos] = $matches["term"];
		}

		$this->_queryTerms["kinds"] = array_unique($this->_queryTerms["kinds"]);
		if(count($this->_queryTerms["kinds"]) == 0) {
			$this->_queryTerms["kinds"] = array("comics", "images");
		}

		$this->_analyzed = true;
	}
	public function getItems(WCGroup &$group) {
		return array(
			"comics" => $this->getComics($group),
			"images" => $this->getImages($group)
		);
	}
	public function queryString() {
		$out = "";

		if($this->analyzed()) {
			$terms = array();

			if(!$this->_queryTerms["anykind"]) {
				foreach($this->_queryTerms["kinds"] as $kind) {
					$terms[] = "only:{$kind}";
				}
			}

			foreach(array(self::OperationSearch, self::OperationFilter) as $operation) {
				$opPrefix = $operation == self::OperationFilter ? "-" : "";
				foreach(array(self::TypeCollection, self::TypeDirectory, self::TypeTag) as $type) {
					$typePrefix = $type == self::TypeTag ? "" : "{$type}:";
					foreach(array(self::ModeExact, self::ModePartial) as $mode) {
						foreach($this->_queryTerms[$operation][$type][$mode] as $term) {
							$terms[] = "{$opPrefix}{$typePrefix}{$term}";
						}
					}
				}
			}

			array_walk($terms, function(&$item) {
				$item = str_replace(" ", "_", $item);
			});

			$out = implode(" ", $terms);
		}

		return $out;
	}
	//
	// Protected methods.
	protected function completeQueryTermsStruct() {
		foreach(array(self::OperationSearch, self::OperationFilter) as $operation) {
			$this->_queryTerms[$operation] = array();
			foreach(array(self::TypeCollection, self::TypeDirectory, self::TypeTag) as $type) {
				$this->_queryTerms[$operation][$type] = array();
				foreach(array(self::ModeExact, self::ModePartial) as $mode) {
					$this->_queryTerms[$operation][$type][$mode] = array();
				}
			}
		}
	}
	/**
	 * 
	 * @param WCGroup $group
	 * @return WCComic[]
	 */
	protected function getComics(WCGroup &$group) {
		$out = array();

		if($this->analyzed() && in_array("comics", $this->_queryTerms["kinds"])) {
			$searchIds = $this->getComicIdsOperation(self::OperationSearch, $group);
			$filterIds = $this->getComicIdsOperation(self::OperationFilter, $group);

			$ids = array_diff($searchIds, $filterIds);
//DEBUG
//			var_dump(count($searchIds));
//			var_dump(count($filterIds));
//			var_dump(count($ids));
//			die;
//DEBUG
			$holder = WCComicsHolder::I();
			foreach($ids as $id) {
				$item = $holder->item($id);
				if($item && $item->ok()) {
					$out[] = $item;
				}
			}
		}

		return $out;
	}
	/**
	 * 
	 * @param string $operation
	 * @param WCGroup $group
	 * @return int[]
	 */
	protected function getComicIdsOperation($operation, WCGroup &$group) {
		$max = 0;
		$auxids = array();
		$this->getComicIdsTypeTags($auxids, $max, $operation, $group);
		$this->getComicIdsTypeDirectories($auxids, $max, $operation, $group);

		if($operation == self::OperationSearch) {
			$auxids = array_filter($auxids, create_function('$item', 'return $item == '.$max.';'));
		}
		return array_keys($auxids);
	}
	protected function getComicIdsTypeDirectories(&$auxids, &$max, $operation, WCGroup &$group) {
		// @{
		$stmtExact = null;
		$stmtId = __CLASS__."::".__FUNCTION__."[di-exact]";
		if($this->_db->has($stmtId)) {
			$stmtExact = $this->_db->get($stmtId);
		} else {
			$query = "select  cmc_id as id\n";
			$query.= "from    {$this->_dbprefix}comics\n";
			$query.= "where   cmc_directory in (\n";
			$query.= "                select  dir_id\n";
			$query.= "                from    {$this->_dbprefix}directories\n";
			$query.= "                where	  dir_group       = :group\n";
			$query.= "                 and 	  lower(dir_name) = :name\n";
			$query.= "        )\n";

			$stmtExact = $this->_db->prepare($stmtId, $query);
		}
		// @}
		// @{
		$stmtPartial = null;
		$stmtId = __CLASS__."::".__FUNCTION__."[di-partial]";
		if($this->_db->has($stmtId)) {
			$stmtPartial = $this->_db->get($stmtId);
		} else {
			$query = "select  distinct cmc_id as id\n";
			$query.= "from    {$this->_dbprefix}comics\n";
			$query.= "where   cmc_directory in (\n";
			$query.= "                select  dir_id\n";
			$query.= "                from    {$this->_dbprefix}directories\n";
			$query.= "                where	  dir_group       = :group\n";
			$query.= "                 and 	  lower(dir_name) like :name\n";
			$query.= "        )\n";

			$stmtPartial = $this->_db->prepare($stmtId, $query);
		}
		// @}

		$params = array(
			":group" => $group->id()
		);
		foreach($this->_queryTerms[$operation][self::TypeDirectory][self::ModeExact] as $name) {
			$max++;
			$params[":name"] = $name;
//DEBUG
//			var_dump($params);
//DEBUG
			if($stmtExact->execute($params) && $stmtExact->rowCount() > 0) {
				foreach($stmtExact->fetchAll() as $row) {
					$id = $row["id"];
					if(!isset($auxids[$id])) {
						$auxids[$id] = 1;
					} else {
						$auxids[$id] ++;
					}
				}
			}
		}
		foreach($this->_queryTerms[$operation][self::TypeDirectory][self::ModePartial] as $name) {
			$max++;
			$params[":name"] = str_replace("*", "%", $name);
//DEBUG
//			var_dump($params);
//			var_dump($stmtPartial->queryString);
//DEBUG
			if($stmtPartial->execute($params) && $stmtPartial->rowCount() > 0) {
				foreach($stmtPartial->fetchAll() as $row) {
					$id = $row["id"];
					if(!isset($auxids[$id])) {
						$auxids[$id] = 1;
					} else {
						$auxids[$id] ++;
					}
				}
			}
		}
	}
	protected function getComicIdsTypeTags(&$auxids, &$max, $operation, WCGroup &$group) {
		// @{
		$stmtExact = null;
		$stmtId = __CLASS__."::".__FUNCTION__."[ta-exact]";
		if($this->_db->has($stmtId)) {
			$stmtExact = $this->_db->get($stmtId);
		} else {
			$query = "select  mem_comic as id\n";
			$query.= "from    {$this->_dbprefix}tag_comics\n";
			$query.= "where   mem_tag in (\n";
			$query.= "                select  tag_id\n";
			$query.= "                from    {$this->_dbprefix}tags\n";
			$query.= "                where	  tag_group       = :group\n";
			$query.= "                 and 	  lower(tag_name) = :name\n";
			$query.= "        )\n";

			$stmtExact = $this->_db->prepare($stmtId, $query);
		}
		// @}
		// @{
		$stmtPartial = null;
		$stmtId = __CLASS__."::".__FUNCTION__."[ta-partial]";
		if($this->_db->has($stmtId)) {
			$stmtPartial = $this->_db->get($stmtId);
		} else {
			$query = "select  distinct mem_comic as id\n";
			$query.= "from    {$this->_dbprefix}tag_comics\n";
			$query.= "where   mem_tag in (\n";
			$query.= "                select  tag_id\n";
			$query.= "                from    {$this->_dbprefix}tags\n";
			$query.= "                where	  tag_group       = :group\n";
			$query.= "                 and 	  lower(tag_name) like :name\n";
			$query.= "        )\n";

			$stmtPartial = $this->_db->prepare($stmtId, $query);
		}
		// @}

		$params = array(
			":group" => $group->id()
		);
		foreach($this->_queryTerms[$operation][self::TypeTag][self::ModeExact] as $name) {
			$max++;
			$params[":name"] = $name;
			if($stmtExact->execute($params) && $stmtExact->rowCount() > 0) {
				foreach($stmtExact->fetchAll() as $row) {
					if(!isset($auxids[$row["id"]])) {
						$auxids[$row["id"]] = 1;
					} else {
						$auxids[$row["id"]] ++;
					}
				}
			}
		}
		foreach($this->_queryTerms[$operation][self::TypeTag][self::ModePartial] as $name) {
			$max++;
			$params[":name"] = str_replace("*", "%", $name);
			if($stmtPartial->execute($params) && $stmtPartial->rowCount() > 0) {
				foreach($stmtPartial->fetchAll() as $row) {
					if(!isset($auxids[$row["id"]])) {
						$auxids[$row["id"]] = 1;
					} else {
						$auxids[$row["id"]] ++;
					}
				}
			}
		}
	}
	/**
	 * 
	 * @param WCGroup $group
	 * @return WCImage[]
	 */
	protected function getImages(WCGroup &$group) {
		$out = array();

		if($this->analyzed() && in_array("images", $this->_queryTerms["kinds"])) {
			$searchIds = $this->getImageIdsOperation(self::OperationSearch, $group);
			$filterIds = $this->getImageIdsOperation(self::OperationFilter, $group);

			$ids = array_diff($searchIds, $filterIds);
			$holder = WCImagesHolder::I();
			foreach($ids as $id) {
				$item = $holder->item($id);
				if($item && $item->ok()) {
					$out[] = $item;
				}
			}
		}

		return $out;
	}
	/**
	 * 
	 * @param string $operation
	 * @param WCGroup $group
	 * @return int[]
	 */
	protected function getImageIdsOperation($operation, WCGroup &$group) {
		$max = 0;
		$auxids = array();
		$this->getImageIdsTypeTags($auxids, $max, $operation, $group);
		$this->getImageIdsTypeDirectories($auxids, $max, $operation, $group);

		if($operation == self::OperationSearch) {
			$auxids = array_filter($auxids, create_function('$item', 'return $item == '.$max.';'));
		}
		return array_keys($auxids);
	}
	protected function getImageIdsTypeDirectories(&$auxids, &$max, $operation, WCGroup &$group) {
		// @{
		$stmtExact = null;
		$stmtId = __CLASS__."::".__FUNCTION__."[di-exact]";
		if($this->_db->has($stmtId)) {
			$stmtExact = $this->_db->get($stmtId);
		} else {
			$query = "select  img_id as id\n";
			$query.= "from    {$this->_dbprefix}images\n";
			$query.= "where   img_directory in (\n";
			$query.= "                select  dir_id\n";
			$query.= "                from    {$this->_dbprefix}directories\n";
			$query.= "                where	  dir_group       = :group\n";
			$query.= "                 and 	  lower(dir_name) = :name\n";
			$query.= "        )\n";

			$stmtExact = $this->_db->prepare($stmtId, $query);
		}
		// @}
		// @{
		$stmtPartial = null;
		$stmtId = __CLASS__."::".__FUNCTION__."[di-partial]";
		if($this->_db->has($stmtId)) {
			$stmtPartial = $this->_db->get($stmtId);
		} else {
			$query = "select  distinct img_id as id\n";
			$query.= "from    {$this->_dbprefix}images\n";
			$query.= "where   img_directory in (\n";
			$query.= "                select  dir_id\n";
			$query.= "                from    {$this->_dbprefix}directories\n";
			$query.= "                where	  dir_group       = :group\n";
			$query.= "                 and 	  lower(dir_name) like :name\n";
			$query.= "        )\n";

			$stmtPartial = $this->_db->prepare($stmtId, $query);
		}
		// @}

		$params = array(
			":group" => $group->id()
		);
		foreach($this->_queryTerms[$operation][self::TypeDirectory][self::ModeExact] as $name) {
			$max++;
			$params[":name"] = $name;
			if($stmtExact->execute($params) && $stmtExact->rowCount() > 0) {
				foreach($stmtExact->fetchAll() as $row) {
					$id = $row["id"];
					if(!isset($auxids[$id])) {
						$auxids[$id] = 1;
					} else {
						$auxids[$id] ++;
					}
				}
			}
		}
		foreach($this->_queryTerms[$operation][self::TypeDirectory][self::ModePartial] as $name) {
			$max++;
			$params[":name"] = str_replace("*", "%", $name);
			if($stmtPartial->execute($params) && $stmtPartial->rowCount() > 0) {
				foreach($stmtPartial->fetchAll() as $row) {
					$id = $row["id"];
					if(!isset($auxids[$id])) {
						$auxids[$id] = 1;
					} else {
						$auxids[$id] ++;
					}
				}
			}
		}
	}
	protected function getImageIdsTypeTags(&$auxids, &$max, $operation, WCGroup &$group) {
		// @{
		$stmtExact = null;
		$stmtId = __CLASS__."::".__FUNCTION__."[ta-exact]";
		if($this->_db->has($stmtId)) {
			$stmtExact = $this->_db->get($stmtId);
		} else {
			$query = "select  mem_image as id\n";
			$query.= "from    {$this->_dbprefix}tag_images\n";
			$query.= "where   mem_tag in (\n";
			$query.= "                select  tag_id\n";
			$query.= "                from    {$this->_dbprefix}tags\n";
			$query.= "                where	  tag_group       = :group\n";
			$query.= "                 and 	  lower(tag_name) = :name\n";
			$query.= "        )\n";

			$stmtExact = $this->_db->prepare($stmtId, $query);
		}
		// @}
		// @{
		$stmtPartial = null;
		$stmtId = __CLASS__."::".__FUNCTION__."[ta-partial]";
		if($this->_db->has($stmtId)) {
			$stmtPartial = $this->_db->get($stmtId);
		} else {
			$query = "select  distinct mem_image as id\n";
			$query.= "from    {$this->_dbprefix}tag_images\n";
			$query.= "where   mem_tag in (\n";
			$query.= "                select  tag_id\n";
			$query.= "                from    {$this->_dbprefix}tags\n";
			$query.= "                where	  tag_group       = :group\n";
			$query.= "                 and 	  lower(tag_name) like :name\n";
			$query.= "        )\n";

			$stmtPartial = $this->_db->prepare($stmtId, $query);
		}
		// @}

		$params = array(
			":group" => $group->id()
		);
		foreach($this->_queryTerms[$operation][self::TypeTag][self::ModeExact] as $name) {
			$max++;
			$params[":name"] = $name;
			if($stmtExact->execute($params) && $stmtExact->rowCount() > 0) {
				foreach($stmtExact->fetchAll() as $row) {
					$id = $row["id"];
					if(!isset($auxids[$id])) {
						$auxids[$id] = 1;
					} else {
						$auxids[$id] ++;
					}
				}
			}
		}
		foreach($this->_queryTerms[$operation][self::TypeTag][self::ModePartial] as $name) {
			$max++;
			$params[":name"] = str_replace("*", "%", $name);
			if($stmtPartial->execute($params) && $stmtPartial->rowCount() > 0) {
				foreach($stmtPartial->fetchAll() as $row) {
					$id = $row["id"];
					if(!isset($auxids[$id])) {
						$auxids[$id] = 1;
					} else {
						$auxids[$id] ++;
					}
				}
			}
		}
	}
	//
	// Public class methods.
	//
	// Protected class methods.
}
